//Copyright 2010 Balazs Keri, Imre Keri
//
//This file is part of FireworksCreator.
//
//FireworksCreator is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//FireworksCreator is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with FireworksCreator.  If not, see <http://www.gnu.org/licenses/>.
//
#include <common/identified.h>
#include <model/root.h>
#include <model/group.h>
#include <model/particletemplate.h>
#include <model/emittertemplate.h>
#include <model/shooter.h>
#include "modeldataobjectadapter.h"
#include "primitives.h"
#include "primitivegroup.h"
#include "attrevent.h"
#include "viewattributes.h"
#include "actionstate.h"
#include "mainview.h"
#include "objtreeview.h"
//
class ObjTreeView::Item:public QTreeWidgetItem
{
public:
   Item(const Identified *object)
   :
      QTreeWidgetItem(UserType),
      object(object),
      adapter(ModelDataObjectAdapter::get_adapter(object->metaObject()->className()))
   {
      update_item();
      setIcon(0,adapter->get_type_icon());
   }
   const Identified *get_object() const
   {
      return object;
   }
   Identified *get_object()
   {
      return const_cast<Identified*>(object);
   }
   Id get_object_id() const
   {
      return object->get_id();
   }
   Item *child_item(int i) const
   {
      return static_cast<Item*>(child(i));
   }
   void update_item()
   {
      setText(0,adapter->get_object_name(object));
   }
private:
   const Identified * const object;
   ModelDataObjectAdapter * const adapter;
};
//
//
//
ObjTreeView::ObjTreeView(MainView *main_view,const QMetaObject *filter_type,bool groups,bool recursive)
:
   QTreeWidget(),
   filter_type(filter_type),
   groups(groups),
   recursive(recursive)
{
   setHeaderHidden(true);
   setRootIsDecorated(recursive);
   connect(this,SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),this,SLOT(on_currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
   connect(this,SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),this,SLOT(on_itemDoubleClicked(QTreeWidgetItem*,int)));
   new_pt_state=new ActionState(main_view->get_act_dispatcher(),main_view->ui.action_create_particle_template,this);
   connect(new_pt_state,SIGNAL(triggered()),this,SLOT(on_new_pt()));
   new_et_state=new ActionState(main_view->get_act_dispatcher(),main_view->ui.action_create_emitter_template,this);
   connect(new_et_state,SIGNAL(triggered()),this,SLOT(on_new_et()));
   new_g_state=new ActionState(main_view->get_act_dispatcher(),main_view->ui.action_create_group,this);
   connect(new_g_state,SIGNAL(triggered()),this,SLOT(on_new_g()));
   delete_state=new ActionState(main_view->get_act_dispatcher(),main_view->ui.action_delete,this);
   connect(delete_state,SIGNAL(triggered()),this,SLOT(on_delete()));
}
ObjTreeView::~ObjTreeView()
{
   clear();
}
//
void ObjTreeView::activate_actions()
{
   new_pt_state->activate();
   new_et_state->activate();
   new_g_state->activate();
   delete_state->activate();
}
void ObjTreeView::deactivate_actions()
{
   new_pt_state->deactivate();
   new_et_state->deactivate();
   new_g_state->deactivate();
   delete_state->deactivate();
}
void ObjTreeView::set_current(Identified *current)
{
   if(item.contains(current->get_id()))
   {
      setCurrentItem(item.value(current->get_id()));
   }
}
void ObjTreeView::on_currentItemChanged(QTreeWidgetItem *current,QTreeWidgetItem *previous)
{
   if(current)
      emit current_changed(static_cast<Item*>(current)->get_object());
   else
      emit current_changed(0);
   delete_state->set_enabled(current!=0);
}
void ObjTreeView::on_itemDoubleClicked(QTreeWidgetItem *item,int column)
{
   if(item)
   {
      emit double_clicked(static_cast<Item*>(item)->get_object());
   }
}
void ObjTreeView::on_new_pt()
{
   create_object(&model::ParticleTemplate::staticMetaObject);
}
void ObjTreeView::on_new_et()
{
   create_object(&model::EmitterTemplate::staticMetaObject);
}
void ObjTreeView::on_new_g()
{
   create_object(&model::Group::staticMetaObject);
}
void ObjTreeView::on_delete()
{
   Item * const it=static_cast<Item*>(currentItem());
   if(it)
   {
      delete_object(it->get_object());
   }
}
//
void ObjTreeView::handle_object_changed(Identified *old_object)
{
   if(old_object)
   {
      depopulate_item_data(invisibleRootItem());
   }
   bool const has_object=(this->get_object()!=0);
   if(has_object)
   {
      populate_item_data(invisibleRootItem());
   }
   new_pt_state->set_enabled(has_object);
   new_et_state->set_enabled(has_object);
   new_g_state->set_enabled(has_object);
}
void ObjTreeView::handle_post_primitive(const Primitive &prim)
{
   using namespace Primitives;
   switch(prim.type)
   {
   case type_Reset:
      set_object(0);
      break;
   case type_SetDataObjectName:
      {
         const SetDataObjectName& p=static_cast<const SetDataObjectName&>(prim);
         ItemHash::iterator itr=item.find(p.get_object_id());
         while(itr!=item.end()&&itr.key()==p.get_object_id())
         {
            itr.value()->update_item();
            ++itr;
         }
      }
      break;
   case type_InsertContainment:
      {
         const InsertContainment& p=static_cast<const InsertContainment&>(prim);
         if(p.was_undo())
            handle_remove_containment(p.get_parent_id(),p.get_child_id());
         else
            handle_insert_containment(p.get_parent_id(),p.get_child_id());
      }
      break;
   case type_NewDataObject:
      {
         const NewDataObject& p=static_cast<const NewDataObject&>(prim);
         if(p.was_undo())
            handle_remove_containment(p.get_new_parent_id(),p.get_new_object_id());
         else
            handle_insert_containment(p.get_new_parent_id(),p.get_new_object_id());
      }
      break;
   case type_RemoveContainment:
      {
         const RemoveContainment& p=static_cast<const RemoveContainment&>(prim);
         if(p.was_undo())
            handle_insert_containment(p.get_parent_id(),p.get_child_id());
         else
            handle_remove_containment(p.get_parent_id(),p.get_child_id());
      }
      break;
   case type_DelDataObject:
      {
         const DelDataObject& p=static_cast<const DelDataObject&>(prim);
         if(p.was_undo())
         {
            const RemoveObjectFromContainment::SavedParentType &sp=p.get_remove_data().get_parents_data();
            for(RemoveObjectFromContainment::SavedParentType::const_iterator itr=sp.begin();itr!=sp.end();++itr)
            {
               handle_insert_containment(itr->first,p.get_old_object_id());
            }
         }
         else
         {
            const RemoveObjectFromContainment::SavedParentType &sp=p.get_remove_data().get_parents_data();
            for(RemoveObjectFromContainment::SavedParentType::const_iterator itr=sp.begin();itr!=sp.end();++itr)
            {
               handle_remove_containment(itr->first,p.get_old_object_id());
            }
         }
      }
      break;
   case type_MoveChild:
      {
         const MoveChild& p=static_cast<const MoveChild&>(prim);
         if(p.was_undo())
            handle_move_child(p.get_parent_id(),p.get_new_position(),p.get_old_position());
         else
            handle_move_child(p.get_parent_id(),p.get_old_position(),p.get_new_position());
      }
      break;
   }
}
//
void ObjTreeView::focusInEvent(QFocusEvent *event)
{
   activate_actions();
}
//
void ObjTreeView::handle_insert_containment(Id parent_id,Id child_id)
{
   Identified * const parent=get_document()->lookup_object(parent_id);
   Identified * const child=get_document()->lookup_object(child_id);
   if(!test_filter(child))
      return;
   int childindex=get_child_index(parent,child);
   if(recursive)
   {
      ItemHash::iterator itr=item.find(parent_id);
      while(itr!=item.end()&&itr.key()==parent_id)
      {
         Item * const parit=itr.value();
         Item * const chit=new Item(child);
         item.insert(child_id,chit);
         parit->insertChild(childindex,chit);
         populate_item_data(chit);
         ++itr;
      }
   }
   if(parent==get_object())
   {
      Item * const chit=new Item(child);
      item.insert(child_id,chit);
      insertTopLevelItem(childindex,chit);
      if(recursive)
         populate_item_data(chit);
   }
}
void ObjTreeView::handle_remove_containment(Id parent_id,Id child_id)
{
   QList<Item*> chitlist=item.values(child_id);
   for(int i=0;i<chitlist.size();++i)
   {
      Item * const chit=chitlist.at(i);
      QTreeWidgetItem *parit=chit->parent();
      Identified *par;
      if(!parit||parit==invisibleRootItem())
      {
         parit=invisibleRootItem();
         par=get_object();
      }
      else
      {
         par=static_cast<Item*>(parit)->get_object();
      }
      if(par->get_id()==parent_id)
      {
         depopulate_item_data(chit);
         parit->removeChild(chit);
         item.remove(child_id,chit);
         delete chit;
      }
   }
}
void ObjTreeView::handle_move_child(Id parent_id,int from,int to)
{
   Identified * const parent=get_document()->lookup_object(parent_id);
   //if(parent!=root&&!item.contains(parent_id))
   //   return;
   Identified * const child=get_document()->get_root()->child.value(parent).at(to);
   QList<Item*> chitlist=item.values(child->get_id());
   for(int i=0;i<chitlist.size();++i)
   {
      Item * const chit=chitlist.at(i);
      QTreeWidgetItem *parit=chit->parent();
      if(!parit)
         parit=invisibleRootItem();
      Id const parid=((parit==invisibleRootItem())?get_object()->get_id():static_cast<Item*>(parit)->get_object_id());
      if(parid==parent_id)
      {
         parit->removeChild(chit);
         parit->insertChild(get_child_index(parent,child),chit);
      }
   }
}
void ObjTreeView::populate_item_data(QTreeWidgetItem *parent)
{
   using namespace model;
   const Root * const r=get_document()->get_root();
   Identified * const parobj=((parent==invisibleRootItem())?get_object():static_cast<Item*>(parent)->get_object());
   Root::IdentifiedList const children=r->child.value(parobj);
   for(Root::IdentifiedList::const_iterator child=children.begin();child!=children.end();++child)
   {
      const Identified* const ch=*child;
      if(test_filter(ch))
      {
         Item *it=new Item(ch);
         item.insert(ch->get_id(),it);
         parent->addChild(it);
         if(recursive)
            populate_item_data(it);
      }
   }
}
void ObjTreeView::depopulate_item_data(QTreeWidgetItem *parent)
{
   if(parent==invisibleRootItem())
   {
      clear();
      item.clear();
   }
   else
   {
      for(int i=parent->childCount()-1;i>=0;--i)
      {
         Item * const it=static_cast<Item*>(parent->child(i));
         depopulate_item_data(it);
         item.remove(it->get_object_id(),it);
         delete it;
      }
   }
}
bool ObjTreeView::test_filter(const Identified *object) const
{
   const QMetaObject * const o=object->metaObject();
   if(o==&model::Group::staticMetaObject)
   {
      return groups;
   }
   return !filter_type||o==filter_type;
}
int ObjTreeView::get_child_index(const Identified *parent,const Identified *child) const
{
   model::Root::IdentifiedList const children=get_document()->get_root()->child.value(const_cast<Identified*>(parent));
   int i=0;
   for(model::Root::IdentifiedList::const_iterator chitr=children.begin();chitr!=children.end();++chitr)
   {
      if(*chitr==child)
         break;
      if(test_filter(*chitr))
         ++i;
   }
   return i;
}
void ObjTreeView::create_object(const QMetaObject *type)
{
   ModelDataObjectAdapter * const adap=ModelDataObjectAdapter::get_adapter(type->className());
   QScopedPointer<PrimitiveGroup> group(new PrimitiveGroup(get_document()));
   Primitives::NewDataObject *create_prim;
   QString name=tr("new %1").arg(adap->get_short_name());
   group->set_description(tr("create new particle template '%1'","commands").arg(name));
   try
   {
      group->add_exec(create_prim=new Primitives::NewDataObject(*adap,get_document(),get_object(),-1));
      Identified *new_obj=create_prim->get_new_object(get_document());
      group->add_exec(new Primitives::SetDataObjectName(*adap,get_document(),new_obj,name));
      group->finalize();
   }
   catch(UiError&)
   {
      return;
   }
   group.take();
}
void ObjTreeView::delete_object(Identified *object)
{
}
